
//  Copyright (C) 2009-2011 Luca Piccioni
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
// 
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;

namespace Derm.Raster
{
	/// <summary>
	/// Known image container format enumeration.
	/// </summary>
	public enum ImageFormat
	{
		/// <summary>
		/// Windows bitmap container.
		/// </summary>
		Bitmap,
		/// <summary>
		/// Windows icon.
		/// </summary>
		Ico,
		/// <summary>
		/// Independent JPEG Group container.
		/// </summary>
		Jpeg,
		/// <summary>
		/// JPEG-2000 container.
		/// </summary>
		Jpeg2000,
		/// <summary>
		/// JPEG Network Graphics container.
		/// </summary>
		Jng,
		/// <summary>
		/// Multiple Network Graphics container.
		/// </summary>
		Mng,
		/// <summary>
		/// Portable Network Graphics container.
		/// </summary>
		Png,
		/// <summary>
		/// Tagged Image File container.
		/// </summary>
		Tiff,
		/// <summary>
		/// Truevision Targa container.
		/// </summary>
		Targa,
		/// <summary>
		/// EXIF container.
		/// </summary>
		Exif,
		/// <summary>
		/// GIF container.
		/// </summary>
		Gif,
		/// <summary>
		/// EXR container.
		/// </summary>
		Exr,
		/// <summary>
		/// HDR container.
		/// </summary>
		Hdr,
		/// <summary>
		/// RAD container.
		/// </summary>
		Rad,
		/// <summary>
		/// Portable bitmap.
		/// </summary>
		Pbm,
		/// <summary>
		/// Portable pixelmap
		/// </summary>
		Ppm,
		/// <summary>
		/// Portable graymap
		/// </summary>
		Pgm,
		/// <summary>
		/// Portable floatmap
		/// </summary>
		Pfm,
		/// <summary>
		/// Kodak PhotoCD container.
		/// </summary>
		KodakPcd,
		/// <summary>
		/// Zsoft Paintbrush PCX bitmap container
		/// </summary>
		Pcx,
		/// <summary>
		/// Commodore 64 KOALA container.
		/// </summary>
		Koala,
		/// <summary>
		/// Amiga IFF container.
		/// </summary>
		Iff,
		/// <summary>
		/// SUN raster container.
		/// </summary>
		Ras,
		/// <summary>
		/// Machintosh picture container.
		/// </summary>
		MacPict,
		/// <summary>
		/// RAW camera container.
		/// </summary>
		Raw,
		/// <summary>
		/// Unknown container.
		/// </summary>
		Unknown
	}

	/// <summary>
	/// Image coder/decoder.
	/// </summary>
	public sealed class ImageCodec : PluginLoader<IImageCodecPlugin>
	{
		#region Constructors

		/// <summary>
		/// Static constructor.
		/// </summary>
		static ImageCodec()
		{
			// Initialize image format description
			InitializeImageFormatDescription();
			// Initialize image format extentions
			InitializeImageFormatExtensions();
		}
		
		/// <summary>
		/// This type is a singleton.
		/// </summary>
		private ImageCodec()
		{
			// Core plugin always registered
			RegisterPlugin(new NetImagingImageCodecPlugin());

			// Register plugin placed in the current directory
			LoadImageCodecPlugins(Directory.GetCurrentDirectory(), PluginFactoryType);
			// Register plugin placed in %DERM_IMAGE_CODEC_DIR%
			string dermImageCodecDir = Environment.GetEnvironmentVariable("DERM_IMAGE_CODEC_DIR");
			if ((dermImageCodecDir != null) && (Directory.Exists(dermImageCodecDir) == true))
				LoadImageCodecPlugins(dermImageCodecDir, PluginFactoryType);
		}

		/// <summary>
		/// The type which following plugin factory conventions.
		/// </summary>
		private const string PluginFactoryType = "Derm.ImageCodecFactory";
		
		#endregion

		#region Singleton

		/// <summary>
		/// The only instance of ImageCodec.
		/// </summary>
		public static readonly ImageCodec Instance = new ImageCodec();

		#endregion

		#region Image Format Management

		/// <summary>
		/// Get the short description of an ImageFormat.
		/// </summary>
		/// <param name="format">
		/// The <see cref="ImageFormat"/> to describe shortly.
		/// </param>
		/// <returns>
		/// It returns a string describing shortly <paramref name="format"/>. Tipically it is the most common
		/// file extention in upper case.
		/// </returns>
		public static string GetImageFormatBriefDescription(ImageFormat format)
		{
			switch (format) {
				case ImageFormat.Unknown:
					return (String.Empty);
				default:
					return (format.ToString().ToUpper());
			}
		}

		/// <summary>
		/// Get the short description of an ImageFormat.
		/// </summary>
		/// <param name="format">
		/// The <see cref="ImageFormat"/> to describe shortly.
		/// </param>
		/// <returns>
		/// It returns a string describing shortly <paramref name="format"/>. Tipically it is the most common
		/// file extention in upper case.
		/// </returns>
		public static string GetImageFormatDescription(ImageFormat format)
		{
			return (sImageFormatDescription[format]);
		}

		/// <summary>
		/// Determine the image format from a path.
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/> that specifies the path to test for determining the image format.
		/// </param>
		/// <returns>
		/// It returns an <see cref="ImageFormat"/> derived from <paramref name="path"/>. In the case it's not possible to
		/// determine the specific file format, it can return ImageFormat.Unknown.
		/// </returns>
		public static ImageFormat GetImageFormatFromExtension(string path)
		{
			if (path == null)
				throw new ArgumentNullException("path");

			// Lowering path
			path = path.ToLower();

			foreach (KeyValuePair<ImageFormat, List<string>> keyvalue in sImageFormatExtensions) {
				if (keyvalue.Value.Exists(delegate (string ext) { return (path.EndsWith(ext)); }))
					return (keyvalue.Key);
			}

			return (ImageFormat.Unknown);
		}

		/// <summary>
		/// Give the most used file extension from an image format.
		/// </summary>
		/// <param name="format">
		/// A <see cref="ImageFormat"/> that specifies the image format.
		/// </param>
		/// <returns>
		/// It returns an enumeration of strings representing the known extensions for <paramref name="format"/>. Each file
		/// extension is prefixed with a '.' character.
		/// </returns>
		public static IEnumerable<string> GetExtensionsFromImageFormat(ImageFormat format)
		{
			return (sImageFormatExtensions[format]);
		}

		/// <summary>
		/// Generate a filter for the .NET framework file dialog.
		/// </summary>
		/// <param name="format">
		/// A <see cref="ImageFormat"/> which defines the file filter.
		/// </param>
		/// <returns>
		/// It returns a string representing a file filter convenient for the .NET Framework file dialog.
		/// </returns>
		public static string GetFileDialogFilter(ImageFormat format)
		{
			IEnumerable<string> extentions = GetExtensionsFromImageFormat(format);
			StringBuilder sb = new StringBuilder();

			sb.AppendFormat("{0}|", GetImageFormatDescription(format));

			foreach (string ext in extentions)
				sb.AppendFormat("{0},", ext);
			sb.Remove(sb.Length-1, 1);

			return (sb.ToString());
		}

		/// <summary>
		/// Describe all supported image formats.
		/// </summary>
		private static void InitializeImageFormatDescription()
		{
			// BMP
			sImageFormatDescription.Add(ImageFormat.Bitmap, "Windows Bitmap");

			// ICO
			sImageFormatDescription.Add(ImageFormat.Ico, "Windows Icon");

			// JPEG
			sImageFormatDescription.Add(ImageFormat.Jpeg, "Joint Photographic Experts Group");

			// JPEG-2000
			sImageFormatDescription.Add(ImageFormat.Jpeg2000, "JPEG 2000");

			// JNG
			sImageFormatDescription.Add(ImageFormat.Jng, "JPEG Network Graphics");

			// MNG
			sImageFormatDescription.Add(ImageFormat.Mng, "Multiple Network Graphics");

			// PNG
			sImageFormatDescription.Add(ImageFormat.Png, "Portable Network Graphics");

			// TIFF
			sImageFormatDescription.Add(ImageFormat.Tiff, "Tagged Image File Format");

			// TARGA
			sImageFormatDescription.Add(ImageFormat.Targa, "Truevision Targa");

			// EXIF
			sImageFormatDescription.Add(ImageFormat.Exif, "Exchangeable Image File");

			// GIF
			sImageFormatDescription.Add(ImageFormat.Gif, "Graphics Interchange Format");

			// EXR
			sImageFormatDescription.Add(ImageFormat.Exr, "ILM OpenEXR");

			// HDR
			sImageFormatDescription.Add(ImageFormat.Hdr, "High Dynamic Range");

			// RAD
			sImageFormatDescription.Add(ImageFormat.Rad, "High Dynamic Range");

			// PBM
			sImageFormatDescription.Add(ImageFormat.Pbm, "Portable Bitmap");

			// PPM
			sImageFormatDescription.Add(ImageFormat.Ppm, "Portable Pixel Map");

			// PGM
			sImageFormatDescription.Add(ImageFormat.Pgm, "Portable Gray Map");

			// PFM
			sImageFormatDescription.Add(ImageFormat.Pfm, "Portable Float Map");

			// PCD
			sImageFormatDescription.Add(ImageFormat.KodakPcd, "Kodac PhotoCD");

			// PCX
			sImageFormatDescription.Add(ImageFormat.Pcx, "Zsoft Paintbrush");

			// KOALA
			sImageFormatDescription.Add(ImageFormat.Koala, "C64 Koala Graphics");

			// IFF
			sImageFormatDescription.Add(ImageFormat.Iff, "IFF Interleaved Bitmap");

			// RAS
			sImageFormatDescription.Add(ImageFormat.Ras, "Sun Raster Image");

			// PICT
			sImageFormatDescription.Add(ImageFormat.MacPict, "Macintosh PICT");

			// RAW
			sImageFormatDescription.Add(ImageFormat.Raw, "Raw Data Digital Photo");

			// UNK
			sImageFormatDescription.Add(ImageFormat.Unknown, "All files");

#if DEBUG
			foreach (object value in Enum.GetValues(typeof(ImageFormat)))
				if (sImageFormatDescription.ContainsKey((ImageFormat)value) == false)
					throw new NotImplementedException("partial ImageFormat description implemented");
#endif
		}

		/// <summary>
		/// Define file extentions for all supported image formats.
		/// </summary>
		private static void InitializeImageFormatExtensions()
		{
			List<string> extList;

			// BMP
			extList = new List<string>();
			extList.Add(".bmp");
			extList.Add(".dib");
			sImageFormatExtensions.Add(ImageFormat.Bitmap, extList);

			// ICO
			extList = new List<string>();
			extList.Add(".ico");
			sImageFormatExtensions.Add(ImageFormat.Ico, extList);

			// JPEG
			extList = new List<string>();
			extList.Add(".jpg");
			extList.Add(".jpeg");
			extList.Add(".jpe");
			extList.Add(".jif");
			sImageFormatExtensions.Add(ImageFormat.Jpeg, extList);

			// JPEG-2000
			extList = new List<string>();
			extList.Add(".j2k");
			extList.Add(".j2c");
			extList.Add(".jp2");
			sImageFormatExtensions.Add(ImageFormat.Jpeg2000, extList);

			// JNG
			extList = new List<string>();
			extList.Add(".jng");
			sImageFormatExtensions.Add(ImageFormat.Jng, extList);

			// MNG
			extList = new List<string>();
			extList.Add(".mng");
			sImageFormatExtensions.Add(ImageFormat.Mng, extList);

			// PNG
			extList = new List<string>();
			extList.Add(".png");
			sImageFormatExtensions.Add(ImageFormat.Png, extList);

			// TIFF
			extList = new List<string>();
			extList.Add(".tiff");
			extList.Add(".tif");
			extList.Add(".tif2");
			sImageFormatExtensions.Add(ImageFormat.Tiff, extList);

			// TARGA
			extList = new List<string>();
			extList.Add(".targa");
			extList.Add(".tga");
			sImageFormatExtensions.Add(ImageFormat.Targa, extList);

			// EXIF
			extList = new List<string>();
			extList.Add(".exif");
			extList.Add(".exig");
			sImageFormatExtensions.Add(ImageFormat.Exif, extList);

			// GIF
			extList = new List<string>();
			extList.Add(".gif");
			sImageFormatExtensions.Add(ImageFormat.Gif, extList);

			// EXR
			extList = new List<string>();
			extList.Add(".exr");
			sImageFormatExtensions.Add(ImageFormat.Exr, extList);

			// HDR
			extList = new List<string>();
			extList.Add(".hdr");
			sImageFormatExtensions.Add(ImageFormat.Hdr, extList);

			// RAD
			extList = new List<string>();
			extList.Add(".rad");
			sImageFormatExtensions.Add(ImageFormat.Rad, extList);

			// PBM
			extList = new List<string>();
			extList.Add(".pbm");
			extList.Add(".pnm");
			sImageFormatExtensions.Add(ImageFormat.Pbm, extList);

			// PPM
			extList = new List<string>();
			extList.Add(".ppm");
			extList.Add(".pnm");
			sImageFormatExtensions.Add(ImageFormat.Ppm, extList);

			// PGM
			extList = new List<string>();
			extList.Add(".pgm");
			extList.Add(".pnm");
			sImageFormatExtensions.Add(ImageFormat.Pgm, extList);

			// PFM
			extList = new List<string>();
			extList.Add(".pfm");
			extList.Add(".pnm");
			sImageFormatExtensions.Add(ImageFormat.Pfm, extList);

			// PCD
			extList = new List<string>();
			extList.Add(".pcd");
			sImageFormatExtensions.Add(ImageFormat.KodakPcd, extList);

			// PCX
			extList = new List<string>();
			extList.Add(".pcx");
			sImageFormatExtensions.Add(ImageFormat.Pcx, extList);

			// KOALA
			extList = new List<string>();
			extList.Add(".koa");
			sImageFormatExtensions.Add(ImageFormat.Koala, extList);

			// IFF
			extList = new List<string>();
			extList.Add(".iff");
			extList.Add(".lbm");
			sImageFormatExtensions.Add(ImageFormat.Iff, extList);

			// RAS
			extList = new List<string>();
			extList.Add(".ras");
			sImageFormatExtensions.Add(ImageFormat.Ras, extList);

			// PICT
			extList = new List<string>();
			extList.Add(".pict");
			sImageFormatExtensions.Add(ImageFormat.MacPict, extList);

			// RAW
			extList = new List<string>();
			extList.Add(".3fr");	// Hasselblad Digital Camera Raw Image Format.
			extList.Add(".arw");	// Sony Digital Camera Raw Image Format for Alpha devices.
			extList.Add(".bay");	// Casio Digital Camera Raw File Format.
			extList.Add(".bmq");	// NuCore Raw Image File.
			extList.Add(".cap");	// Phase One Digital Camera Raw Image Format.
			extList.Add(".cine");	// Phantom Software Raw Image File.
			extList.Add(".cr2");	// Canon Digital Camera RAW Image Format version 2.0. These images are based on the TIFF image standard.
			extList.Add(".crw");	// Canon Digital Camera RAW Image Format version 1.0. 
			extList.Add(".cs1");	// Sinar Capture Shop Raw Image File.
			extList.Add(".dc2");	// Kodak DC25 Digital Camera File.
			extList.Add(".dcr");	// Kodak Digital Camera Raw Image Format for these models: Kodak DSC Pro SLR/c, Kodak DSC Pro SLR/n, Kodak DSC Pro 14N, Kodak DSC PRO 14nx.
			extList.Add(".drf");	// Kodak Digital Camera Raw Image Format.
			extList.Add(".dsc");	// Kodak Digital Camera Raw Image Format.
			extList.Add(".dng");	// Adobe Digital Negative: DNG is publicly available archival format for the raw files generated by digital cameras.
									// By addressing the lack of an open standard for the raw files created by individual camera models, DNG helps ensure that
									// photographers will be able to access their files in the future. 
			extList.Add(".erf");	// Epson Digital Camera Raw Image Format.
			extList.Add(".fff");	// Imacon Digital Camera Raw Image Format.
			extList.Add(".ia");		// Sinar Raw Image File.
			extList.Add(".iiq");	// Phase One Digital Camera Raw Image Format.
			extList.Add(".k25");	// Kodak DC25 Digital Camera Raw Image Format.
			extList.Add(".kc2");	// Kodak DCS200 Digital Camera Raw Image Format.
			extList.Add(".kdc");	// Kodak Digital Camera Raw Image Format.
			extList.Add(".mdc");	// Minolta RD175 Digital Camera Raw Image Format.
			extList.Add(".mef");	// Mamiya Digital Camera Raw Image Format.
			extList.Add(".mos");	// Leaf Raw Image File.
			extList.Add(".mrw");	// Minolta Dimage Digital Camera Raw Image Format.
			extList.Add(".nef");	// Nikon Digital Camera Raw Image Format.
			extList.Add(".nrw");	// Nikon Digital Camera Raw Image Format.
			extList.Add(".orf");	// Olympus Digital Camera Raw Image Format.
			extList.Add(".pef");	// Pentax Digital Camera Raw Image Format.
			extList.Add(".ptx");	// Pentax Digital Camera Raw Image Format.
			extList.Add(".pxn");	// Logitech Digital Camera Raw Image Format.
			extList.Add(".qtk");	// Apple Quicktake 100/150 Digital Camera Raw Image Format.
			extList.Add(".raf");	// Fuji Digital Camera Raw Image Format.
			extList.Add(".raw");	// Panasonic Digital Camera Image Format.
			extList.Add(".rdc");	// Digital Foto Maker Raw Image File.
			extList.Add(".rw2");	// Panasonic LX3 Digital Camera Raw Image Format.
			extList.Add(".rwl");	// Leica Camera Raw Image Format.
			extList.Add(".rwz");	// Rawzor Digital Camera Raw Image Format.
			extList.Add(".sr2");	// Sony Digital Camera Raw Image Format.
			extList.Add(".srf");	// Sony Digital Camera Raw Image Format for DSC-F828 8 megapixel digital camera or Sony DSC-R1
			extList.Add(".sti");	// Sinar Capture Shop Raw Image File.
			sImageFormatExtensions.Add(ImageFormat.Raw, extList);

			// UNK
			extList = new List<string>();
			extList.Add(".*");
			sImageFormatExtensions.Add(ImageFormat.Unknown, extList);

#if DEBUG
			foreach (object value in Enum.GetValues(typeof(ImageFormat)))
				if (sImageFormatExtensions.ContainsKey((ImageFormat)value) == false)
					throw new NotImplementedException("partial ImageFormat description implemented");
#endif
		}

		/// <summary>
		/// Map between image format and description.
		/// </summary>
		static readonly Dictionary<ImageFormat, string> sImageFormatDescription = new Dictionary<ImageFormat, string>();

		/// <summary>
		/// Map between image format and file extention(s).
		/// </summary>
		static readonly Dictionary<ImageFormat, List<string>> sImageFormatExtensions = new Dictionary<ImageFormat, List<string>>();

		#endregion

		#region Codec Criteria

		/// <summary>
		/// String value used for filtering the available plugin list by the plugin name.
		/// </summary>
		public static readonly String PluginName = "PluginName";

		/// <summary>
		/// Floating-point value used by selected plugins to determine some generic quality preset.
		/// </summary>
		/// <remarks>
		/// The plugin can ignore the quality setting specified. The value is normalized in the range [0.0, 1.0], where 1.0 means the
		/// highest quality. Usually this preset shall not reduce fundamental image parameters, such as image extents or pixel precision.
		/// </remarks>
		public static readonly String QualityPreset = "QualityPreset";

		/// <summary>
		/// Boolean value used by selected plugin to determine whether to check for pixel format hardware support.
		/// </summary>
		/// <remarks>
		/// The plugin will check whether the native pixel format is available on current hardware. In the case hardware is not capable
		/// to represent the image and the criteria <see cref="SoftwareSupport"/> is false, it will be throw an exception.
		/// </remarks>
		public static readonly String HardwareSupport = "HardwareSupport";

		/// <summary>
		/// Boolean value used by selected plugin to determine whether to convert image in order to support hardware formats.
		/// </summary>
		/// <remarks>
		/// 
		/// </remarks>
		public static readonly String SoftwareSupport = "SoftwareSupport";

		/// <summary>
		/// Boolean value used by selected plugin to determine whether to support for integer pixel formats.
		/// </summary>
		/// <remarks>
		/// In the case the plugin is able to load Image data with pixel formats defined as integer and this flag is set
		/// to 'true', then the plugin will load data suitable to being loaded into a texture having an integer internal format. This
		/// operation shall be accomplished only in the case the specific image specify a compatible pixel format, even if the current
		/// hardware doesn't offer support for integer textel formats (it's up to the application to check the support availability).
		/// </remarks>
		public static readonly String IntegerFormatSupport = "IntegerFormatSupport";

		/// <summary>
		/// Generic image codec criteria.
		/// </summary>
		public class CodecCriteria
		{
			/// <summary>
			/// Default constructor.
			/// </summary>
			public CodecCriteria()
			{
				// Default values

				// Good compromize between quality and size
				this[QualityPreset] = 0.75f;
				// Do not check hardware support: wider pixel format support
				this[HardwareSupport] = false;
				// Uses software support: wider pixel format support
				this[SoftwareSupport] = true;
				// Do not support integer format: they are not mean for visualization
				this[IntegerFormatSupport] = false;
			}

			/// <summary>
			/// Determine whether a criteria is defined.
			/// </summary>
			/// <param name="cName">
			/// A <see cref="System.String"/> that specifies the criteria value name.
			/// </param>
			/// <returns>
			/// 
			/// </returns>
			public bool IsDefined(string cName)
			{
				if (cName == null)
						throw new ArgumentNullException("cName");

				return (mCriteria.ContainsKey(cName));
			}

			/// <summary>
			/// Get/Set a codec criteria.
			/// </summary>
			/// <param name="cName">
			/// A <see cref="System.String"/> that specifies the criteria value name.
			/// </param>
			public object this[string cName]
			{
				get {
					return (mCriteria[cName]);
				}
				set {
					if (cName == null)
						throw new ArgumentNullException("cName");
					if (value == null)
						throw new ArgumentNullException("value");

					mCriteria.Add(cName, value);
				}
			}

			/// <summary>
			/// Convert this CodecCriteria to its string representation.
			/// </summary>
			/// <returns></returns>
			public override string ToString()
			{
				StringBuilder sb = new StringBuilder();

				foreach (KeyValuePair<String, object> key in mCriteria)
					sb.AppendFormat("{0}={1};", key.Key, key.Value.ToString());

				return (sb.ToString());
			}

			/// <summary>
			/// Map between criteria and its value.
			/// </summary>
			private Dictionary<String, object> mCriteria = new Dictionary<string,object>();

			internal List<IImageCodecPlugin> FilterPluginsByName(List<IImageCodecPlugin> plugins)
			{
				if (IsDefined(PluginName) == true) {
					return (plugins.FindAll(delegate(IImageCodecPlugin plugin) { return (plugin.Name == (string)this[PluginName]); }));
				} else
					return (plugins);
			}
		}

		#endregion

		#region Image Information Query

		/// <summary>
		/// Query ImageInfo from file.
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="ImageInfo"/>
		/// </returns>
		public ImageInfo QueryImageInfo(string path)
		{
			return (QueryImageInfo(path, new CodecCriteria()));
		}
		
		/// <summary>
		/// Query ImageInfo from file.
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/>
		/// </param>
		/// <param name="criteria">
		/// A <see cref="System.String"/> spoecifying the preferred plugin name to use for loading
		/// the image data.
		/// </param>
		/// <returns>
		/// A <see cref="ImageInfo"/>
		/// </returns>
		public ImageInfo QueryImageInfo(string path, CodecCriteria criteria)
		{
			List<IImageCodecPlugin> plugins = GetSupportedReadPlugins(path);

			// Filter plugins by name
			plugins = criteria.FilterPluginsByName(plugins);

			// Sort plugins by priority
			ImageFormat format = ImageCodec.GetImageFormatFromExtension(path);

			plugins.Sort(delegate(IImageCodecPlugin a, IImageCodecPlugin b) {
				return (b.GetPriority(format).CompareTo(a.GetPriority(format)));
			});

			using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read)) {
				using (CodecPluginStreamWrapper fsWrapper = new CodecPluginStreamWrapper(fs)) {
					return (QueryImageInfo(fsWrapper, plugins, criteria));
				}
			}
		}

		private static ImageInfo QueryImageInfo(Stream stream, IEnumerable<IImageCodecPlugin> plugins, CodecCriteria criteria)
		{
			ImageInfo info = new ImageInfo();
			StringBuilder sb = new StringBuilder();
			bool valid = false;

			if (stream == null)
				throw new ArgumentNullException("stream");
			if (stream.CanRead == false)
				throw new ArgumentException(String.Format("unable to read from stream"));
			if (stream.CanSeek == false)
				throw new ArgumentException(String.Format("stream is not seekable"));
			if (plugins == null)
				throw new ArgumentNullException("plugins");

			// Beatify exception message
			sb.AppendLine();

			// Obtain image information
			foreach (IImageCodecPlugin p in plugins) {
				try {
					// Rewing stream
					stream.Seek(0, SeekOrigin.Begin);
					// Try to get information
					info = p.QueryImageInfo(stream, criteria);
					// Stop query image information
					valid = true;
					break;
				} catch (Exception e) {
					sb.AppendFormat("Plugin {0} cannot query information ({1})", p.Name, e.Message);
					sb.AppendLine();
					sb.AppendFormat("  - Stack trace: {0}", e.StackTrace);
					sb.AppendLine();
				}
			}

			// Don't returns not assigned information
			if (valid == false)
				throw new InvalidOperationException(String.Format("no plugin can query information image, {0}", (sb.Length > 2) ? sb.ToString() : "no available plugins"));
			
			return (info);
		}

		#endregion

		#region Image Loading

		/// <summary>
		/// Load an Image from file.
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="Image"/>
		/// </returns>
		public Image Load(string path)
		{
			return (Load(path, new CodecCriteria()));
		}
		
		/// <summary>
		/// Load an Image from file.
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/>
		/// </param>
		/// <param name="criteria">
		/// A <see cref="System.String"/> spoecifying the preferred plugin name to use for loading
		/// the image data.
		/// </param>
		/// <returns>
		/// A <see cref="Image"/>
		/// </returns>
		public Image Load(string path, CodecCriteria criteria)
		{
			if (criteria == null)
				throw new ArgumentNullException("criteria");
			
			List<IImageCodecPlugin> plugins = GetSupportedReadPlugins(path);

			// Criteria: PluginName
			plugins = criteria.FilterPluginsByName(plugins);

			// Sort plugins by priority
			ImageFormat format = GetImageFormatFromExtension(path);

			plugins.Sort(delegate(IImageCodecPlugin a, IImageCodecPlugin b) {
				return (b.GetPriority(format).CompareTo(a.GetPriority(format)));
			});
			
			// Obtain image pixel data
			using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read)) {
				using (CodecPluginStreamWrapper fsWrapper = new CodecPluginStreamWrapper(fs)) {
					return (Load(fsWrapper, plugins, criteria));
				}
			}
		}

		private static Image Load(Stream stream, IEnumerable<IImageCodecPlugin> plugins, CodecCriteria criteria)
		{
			Image image = null;
			StringBuilder sb = new StringBuilder();

			if (stream == null)
				throw new ArgumentNullException("stream");
			if (stream.CanRead == false)
				throw new ArgumentException("unable to read from stream");
			if (stream.CanSeek == false)
				throw new ArgumentException("stream is not seekable");
			if (plugins == null)
				throw new ArgumentNullException("plugins");
			if (criteria == null)
				throw new ArgumentNullException("criteria");

			// Beatify exception message
			sb.AppendLine();

			// Obtain image pixel data
			foreach (IImageCodecPlugin p in plugins) {
				try {
					// Rewind stream
					stream.Seek(0, SeekOrigin.Begin);
					// Load image pixel data
					image = p.Load(stream, criteria);
					break;
				} catch (Exception e) {
					sb.AppendFormat("Plugin {0} cannot load image ({1})", p.Name, e.Message);
					sb.AppendLine();
					sb.AppendFormat("  - Stack trace: {0}", e.StackTrace);
					sb.AppendLine();
				}
			}

			if (image == null)
				throw new InvalidOperationException(String.Format("no plugin can load image, {0}", (sb.Length > 2) ? sb.ToString() : "no available plugins"));
			
			return (image);
		}

		#endregion

		#region Image Saving

		/// <summary>
		/// 
		/// </summary>
		/// <param name="path"></param>
		/// <param name="image"></param>
		/// <param name="format"></param>
		public void Save(string path, Image image, ImageFormat format)
		{
			Save(path, image, format, null);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="path"></param>
		/// <param name="image"></param>
		/// <param name="format">
		/// 
		/// </param>
		/// <param name="criteria"></param>
		public void Save(string path, Image image, ImageFormat format, CodecCriteria criteria)
		{
			List<IImageCodecPlugin> plugins = GetSupportedWritePlugins(path);

			// Filter supported plugins by criteria
			if (criteria != null)
				plugins = criteria.FilterPluginsByName(plugins);

			// Sort plugins by priority
			plugins.Sort(delegate(IImageCodecPlugin a, IImageCodecPlugin b) {
				return (b.GetPriority(format).CompareTo(a.GetPriority(format)));
			});

			// Serialize image data
			using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write)) {
				Save(fs, image, format, plugins, new CodecCriteria());
			}
		}

		/// <summary>
		/// Save image pixels to a <see cref="System.IO.Stream"/>.
		/// </summary>
		/// <param name="iStream">
		/// A <see cref="System.IO.Stream"/> that stores the image data.
		/// </param>
		/// <param name="image">
		/// A <see cref="Image"/> that holds the data to be stored.
		/// </param>
		/// <param name="format">
		/// A <see cref="ImageFormat"/> that specifies the image format to used for saving <paramref name="image"/>.
		/// </param>
		/// <param name="plugins">
		/// A <see cref="List{IImageCodecPlugin}"/> that specifies the used for loading an image from <paramref name="iStream"/>.
		/// </param>
		/// <param name="criteria">
		/// A <see cref="ImageCodec.CodecCriteria"/> that specifies parameters for loading an image stream.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// This exception is thrown if at least one of the parameters <paramref name="iStream"/>, <paramref name="image"/> or <paramref name="plugins"/>
		/// is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// This exception is thrown if <paramref name="iStream"/> cannot be seek or cannot be written.
		/// </exception>
		/// <exception cref="InvalidOperationException">
		/// This exception is thrown if no plugin (from the available in <paramref name="plugins"/>) cannot save the image <paramref name="image"/> on
		/// the stream <paramref name="iStream"/>.
		/// </exception>
		private static void Save(Stream iStream, Image image, ImageFormat format, IEnumerable<IImageCodecPlugin> plugins, CodecCriteria criteria)
		{
			StringBuilder sb = new StringBuilder();

			if (iStream == null)
				throw new ArgumentNullException("iStream");
			if (image == null)
				throw new ArgumentNullException("image");
			if (plugins == null)
				throw new ArgumentNullException("plugins");
			if (iStream.CanWrite == false)
				throw new ArgumentException(String.Format("unable to write to stream"));
			if (iStream.CanSeek == false)
				throw new ArgumentException(String.Format("unable to seek on stream"));

			// Beatify exception message
			sb.AppendLine();

			// Write image pixel data
			foreach (IImageCodecPlugin p in plugins) {
				try {
					// Position file cursor at beginning
					iStream.Seek(0, SeekOrigin.Current);
					// Save image pixel data
					p.Save(iStream, image, format, criteria);
					return;
				} catch (Exception e) {
					sb.AppendFormat("Plugin {0} cannot save image ({1})", p.Name, e.Message);
					sb.AppendLine();
					sb.AppendFormat("  - Stack trace: {0}", e.StackTrace);
					sb.AppendLine();
				}
			}

			throw new InvalidOperationException(String.Format("no plugin can save image, {0}", (sb.Length > 0) ? sb.ToString() : "no available plugins"));
		}

		#endregion

		#region Image Codec Plugin Management

		/// <summary>
		/// Get a list of plugins supporting reading for specific file.
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/> that specifies the file path for querying image
		/// support-
		/// </param>
		/// <returns>
		/// A <see cref="List{IImageCodecPlugin}"/> of plugin able to handle image data stored in the
		/// file path specified.
		/// </returns>
		private List<IImageCodecPlugin> GetSupportedReadPlugins(string path)
		{
			List<IImageCodecPlugin> plugins = new List<IImageCodecPlugin>();

			if (path == null)
				throw new ArgumentNullException("path");

			// Get plugins handling this file (probe by extension)
			foreach (IImageCodecPlugin p in Plugins) {
				// Check support for each plugin
				if (p.IsReadSupported(path) == true)
					plugins.Add(p);
			}
			
			return (plugins);
		}

		/// <summary>
		/// Get a list of plugins supporting writing for specific file.
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/> that specifies the file path for querying image
		/// support-
		/// </param>
		/// <returns>
		/// A <see cref="List{IImageCodecPlugin}"/> of plugin able to handle image data stored in the
		/// file path specified.
		/// </returns>
		private List<IImageCodecPlugin> GetSupportedWritePlugins(string path)
		{
			List<IImageCodecPlugin> plugins = new List<IImageCodecPlugin>();
			
			// Get plugins handling this file (probe by extension)
			foreach (IImageCodecPlugin p in Plugins) {
				// Check support for each plugin
				if (p.IsReadSupported(path) == true)
					plugins.Add(p);
			}
			
			return (plugins);
		}

		#endregion

		#region Image Codec Stream Wrapper

		/// <summary>
		/// Class for managing stream for image codec plugins.
		/// </summary>
		private class CodecPluginStreamWrapper : Stream
		{
			#region Constructors

			public CodecPluginStreamWrapper(Stream baseStream)
			{
				if (baseStream == null)
					throw new ArgumentNullException("baseStream");
				mBaseStream = baseStream;
			}

			#endregion

			#region Wrapper

			/// <summary>
			/// Base stream wrapped by this instance.
			/// </summary>
			public Stream BaseStream
			{
				get {
					return mBaseStream;
				}
			}

			/// <summary>
			/// Wrapper stream.
			/// </summary>
			private Stream mBaseStream;

			#endregion

			#region Stream Overrides

			public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
			{
				return (mBaseStream.BeginRead(buffer, offset, count, callback, state));
			}

			public override int EndRead(IAsyncResult asyncResult)
			{
				return (mBaseStream.EndRead(asyncResult));
			}

			public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
			{
				return (mBaseStream.BeginWrite (buffer, offset, count, callback, state));
			}

			public override void EndWrite(IAsyncResult asyncResult)
			{
				mBaseStream.EndWrite(asyncResult);
			}

			public override bool CanRead
			{
				get {
					return (mBaseStream.CanRead);
				}
			}

			public override bool CanSeek
			{
				get {
					return (mBaseStream.CanSeek);
				}
			}

			public override bool CanWrite
			{
				get {
					return (mBaseStream.CanWrite);
				}
			}

			public override void Flush()
			{
				mBaseStream.Flush();
			}

			/// <summary>
			/// Do not proxy close request to the underlying base stream.
			/// </summary>
			/// <remarks>
			/// This method implementation allow to create nested stream without closing the underlaying stream automatically
			/// on disposal.
			/// </remarks>
			public override void Close()
			{
				mBaseStream.Flush();
			}

			public override long Length
			{
				get {
					return (mBaseStream.Length);
				}
			}

			public override long Position
			{
				get {
					return (mBaseStream.Position);
				}
				set {
					mBaseStream.Position = value;
				}
			}

			public override int Read(byte[] buffer, int offset, int count)
			{
				return (mBaseStream.Read(buffer, offset, count));
			}

			public override int ReadByte()
			{
				return (mBaseStream.ReadByte());
			}

			public override void Write(byte[] buffer, int offset, int count)
			{
				mBaseStream.Write(buffer, offset, count);
			}

			public override void WriteByte(byte value)
			{
				mBaseStream.WriteByte(value);
			}

			public override long Seek(long offset, SeekOrigin origin)
			{
				return (mBaseStream.Seek(offset, origin));
			}

			public override void SetLength(long value)
			{
				mBaseStream.SetLength(value);
			}

			#endregion
		}

		#endregion

		#region Logging

		/// <summary>
		/// Logger of this class.
		/// </summary>
		private static readonly ILogger sLog = Log.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		#endregion
	}
}
